package model;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.GregorianCalendar;

/**
 * Repräsentiert die Verbindung zur Datenbank
 * 
 * @author Jonas Gutermuth
 */
@SuppressWarnings("serial")
public class EbenenRepository extends ArrayList<Ebene> {

	private static EbenenRepository ebenenRepository = null;
	private static DatenbankSchnittstelle datenbankSchnittstelle;

	/**
	 * Der Konstruktor lädt durch eine Datenbankabfrage die Ebenen in das
	 * Repository.
	 * 
	 * @throws ClassNotFoundException
	 * @throws SQLException
	 */
	private EbenenRepository() throws ClassNotFoundException, SQLException {
		datenbankSchnittstelle = DatenbankSchnittstelle.getInstance();
		addArray(datenbankSchnittstelle.getEbenen());
		System.out.println("Ebenen Repository geladen");
	}

	/**
	 * Fügt dem EbenenRepository ein Array von Ebenen hinzu.
	 * 
	 * @param ebenen
	 *            die Ebenen in Form eines Arrays
	 */
	private void addArray(Ebene[] ebenen) {
		for (int i = 0; i < ebenen.length; i++) {
			this.add(ebenen[i]);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.util.ArrayList#get(int)
	 */
	@Override
	public Ebene get(int i) {
		Ebene temp = super.get(i);

		// TODO prüfen ob aktuell

		return temp;
	}

	/**
	 * Speichert eine Ebene im Repository und in der Datenbank. (Für neue
	 * Versionen einer bestehenden Ebene.)
	 * 
	 * @param ebene
	 *            die zu sichernde Ebene
	 * @throws SQLException
	 * @throws ClassNotFoundException
	 */
	public void saveEbene(Ebene ebene) throws ClassNotFoundException,
			SQLException {
		// arrays
		String[][] db_werte = new String[2][9];
		Benutzer[] verantowlich = ebene.getVerantwortlich();
		Benutzer[] redakteur = ebene.getRedakteur();
		Ebene[] unterelement = ebene.getUnterelement();
		Feld[] feld = ebene.getFeld();
		// felder speichern
		if (feld != null) {
			for (int i = 1; i < feld.length; i++) {
				if (!FelderRepository.getInstance().contains(feld[0])) {
					FelderRepository.getInstance().saveFeld(feld[0]);
				}
			}
		}
		// spaltennamen
		db_werte[0][0] = "id";
		db_werte[0][1] = "name";
		db_werte[0][2] = "prosaId";
		db_werte[0][3] = "verantwortlichIds";
		db_werte[0][4] = "erstellungszeit";
		db_werte[0][5] = "redaktionszustand";
		db_werte[0][6] = "redakteurIds";
		db_werte[0][7] = "unterelementIds";
		db_werte[0][8] = "feldIds";
		// werte
		db_werte[1][0] = "" + ebene.getId();
		db_werte[1][1] = ebene.getName();
		db_werte[1][2] = "" + ebene.getProsaId();
		db_werte[1][3] = "";
		if (verantowlich != null && verantowlich.length > 0) { // array zu
																// id-string
			db_werte[1][3] += verantowlich[0].getId();
			for (int i = 1; i < verantowlich.length; i++) {
				db_werte[1][3] += ";" + verantowlich[i].getId();
			}
		}

		db_werte[1][4] = "" + ebene.getErstellungszeit();
		db_werte[1][5] = "" + ebene.getRedaktionszustand();
		db_werte[1][6] = "";
		if (redakteur != null && redakteur.length > 0) { // array zu id-string
			db_werte[1][6] += redakteur[0].getId();
			for (int i = 1; i < redakteur.length; i++) {
				db_werte[1][6] += ";" + redakteur[i].getId();
			}
		}
		db_werte[1][7] = "";
		if (unterelement != null && unterelement.length > 0) { // array zu
																// id-string
			if (unterelement[0] != null) {
				db_werte[1][7] += unterelement[0].getId();
			}
			for (int i = 1; i < unterelement.length; i++) {
				if (unterelement[i] != null) {
					db_werte[1][7] += ";" + unterelement[i].getId();
				}
			}
		}
		db_werte[1][8] = "";
		if (feld != null && feld.length > 0) { // array zu id-string
			db_werte[1][8] += feld[0].getId();
			for (int i = 1; i < feld.length; i++) {
				db_werte[1][8] += ";" + feld[i].getId();
			}
		}
		// ebene in die datenbank eintragen
		datenbankSchnittstelle.insert("ebene", db_werte);

		this.add(ebene);
	}

	/**
	 * Erstellt eine neue Ebene und speichert sie in der Datenbank und im
	 * Repository. (Nicht für neue Versionen einer bestehenden Ebene.)
	 * 
	 * @param name
	 *            der Name
	 * @param prosaId
	 *            die ID des Prosafeldes
	 * @param redaktionszustand
	 *            der Reaktionszustand
	 * @param verantwortlich
	 *            die Verantwortlichen
	 * @param redakteur
	 *            die Redakteure
	 * @param unterelement
	 *            die Unterelemente
	 * @param feld
	 *            die Felder
	 * @return die neu erstellte Ebene
	 * @throws ClassNotFoundException
	 * @throws SQLException
	 */
	public Ebene newEbene(String name, int prosaId,
			int redaktionszustand, Benutzer[] verantwortlich,
			Benutzer[] redakteur, Ebene[] unterelement, Feld[] feld)
			throws ClassNotFoundException, SQLException {
		// variablen
		int id;
		long erstellungszeit = (new GregorianCalendar()).getTimeInMillis();
		String[][] db_werte = new String[2][8];
		// felder speichern
		if (feld != null) {
			for (int i = 1; i < feld.length; i++) {
				if (!FelderRepository.getInstance().contains(feld[0])) {
					FelderRepository.getInstance().saveFeld(feld[0]);
				}
			}
		}
		// spaltennamen
		db_werte[0][0] = "name";
		db_werte[0][1] = "prosaId";
		db_werte[0][2] = "verantwortlichIds";
		db_werte[0][3] = "erstellungszeit";
		db_werte[0][4] = "redaktionszustand";
		db_werte[0][5] = "redakteurIds";
		db_werte[0][6] = "unterelementIds";
		db_werte[0][7] = "feldIds";
		// werte
		db_werte[1][0] = name;
		db_werte[1][1] = "" + prosaId;
		db_werte[1][2] = "";
		if (verantwortlich != null && verantwortlich.length > 0) { // array zu
																	// id-string
			db_werte[1][2] += verantwortlich[0].getId();
			for (int i = 1; i < verantwortlich.length; i++) {
				db_werte[1][2] += ";" + verantwortlich[0].getId();
			}
		}

		db_werte[1][3] = "" + erstellungszeit;
		db_werte[1][4] = "" + redaktionszustand;
		db_werte[1][5] = "";
		if (redakteur != null && redakteur.length > 0) { // array zu id-string
			db_werte[1][5] += redakteur[0].getId();
			for (int i = 1; i < redakteur.length; i++) {
				db_werte[1][5] += ";" + redakteur[0].getId();
			}
		}
		db_werte[1][6] = "";
		if (unterelement != null && unterelement.length > 0) { // array zu
																// id-string
			db_werte[1][6] += unterelement[0].getId();
			for (int i = 1; i < unterelement.length; i++) {
				db_werte[1][6] += ";" + unterelement[0].getId();
			}
		}
		db_werte[1][7] = "";
		if (feld != null && feld.length > 0) { // array zu id-string
			db_werte[1][7] += feld[0].getId();
			for (int i = 1; i < feld.length; i++) {
				db_werte[1][7] += ";" + feld[0].getId();
			}
		}
		// ebene in die datenbank eintragen
		id = datenbankSchnittstelle.insert("ebene", db_werte);

		Ebene ebene = new Ebene(id, name, prosaId,
				erstellungszeit, redaktionszustand, db_werte[1][2],
				db_werte[1][5], db_werte[1][6], db_werte[1][7]);
		this.add(ebene);
		return ebene;
	}

	/**
	 * Durchsucht das Repository (und damit die Datenbank) nach der Ebenene mit
	 * der übergebenen ID.
	 * 
	 * @param id
	 *            die ID der geforderten Ebene
	 * @return Ebene[]
	 */
	public Ebene[] getEbeneNachId(int id) {
		ArrayList<Ebene> list = new ArrayList<Ebene>();
		for (int i = 0; i < this.size(); i++) {
			if (get(i).getId() == id) {
				list.add(this.get(i));
			}
		}
		return list.toArray(new Ebene[0]);
	}

	/**
	 * Durchsucht das Repository und damit die Datenbank nach der neusten Ebene
	 * mit der übergebenen ID.
	 * 
	 * @param id
	 *            die ID
	 * @return die Ebene
	 */
	public Ebene getNeusteEbeneNachId(int id) {
		Ebene e = null;
		for (int i = 0; i < this.size(); i++) {
			if (get(i).getId() == id) {
				if (e == null) {
					e = this.get(i);
				} else {
					if (e.getErstellungszeit() < get(i).getErstellungszeit()) {
						e = this.get(i);
					}
				}
			}
		}
		return e;
	}

	/**
	 * Durchsucht das Repository und damit die Datenbank nach der neusten Ebene
	 * mit der Datenbank ID und dem Redaktionszustand.
	 * 
	 * @param redaktionszustand
	 *            der Redaktionszustand
	 * @param id
	 *            die ID
	 * @return die Ebene
	 */
	public Ebene getNeusteEbeneNachIdRedakt(int id, int redaktionszustand) {
		Ebene e = null;
		for (int i = 0; i < this.size(); i++) {
			if (get(i).getId() == id && get(i).getRedaktionszustand() == redaktionszustand) {
				if (e == null) {
					e = this.get(i);
				} else {
					if (e.getErstellungszeit() < get(i).getErstellungszeit()) {
						e = this.get(i);
					}
				}
			}
		}
		return e;
	}

	/**
	 * Liefert ein Ebenen-Array der Aufgaben des übergebenen
	 * Benutzers(Verantwortlicher) zurück.
	 * 
	 * @param benutzer
	 *            der Benutzer
	 * @return die Ebenen als Array
	 * @throws SQLException
	 * @throws ClassNotFoundException
	 */
	public Ebene[] getVerantwortlichAufgaben(Benutzer benutzer, int ebenenID) throws ClassNotFoundException, SQLException {
		ArrayList<Ebene> temp = new ArrayList<Ebene>();

		// Ebenen nach zugehoerigen Verantwortlichen durchsuchen		
		Ebene[] unterEbenen = ebenenRepository.getNeusteEbeneNachId(ebenenID).getUnterelement();
		Ebene[] tmp;
		if (unterEbenen != null) {
			for (int i = 0; i < unterEbenen.length; i++) {
				// Prüfen ob diese Ebene eine aktuelle Aufgabe ist
				if (unterEbenen[i].getVerantwortlich() != null) {
					for (int j = 0; j < unterEbenen[i].getVerantwortlich().length; j++) {
						if (unterEbenen[i].getVerantwortlich()[j] == benutzer) {
							if (unterEbenen[i].getRedaktionszustand() == Ebene.ZUSTAND_UNBEARBEITET)
								temp.add(unterEbenen[i]);
						}	
					}
				}
				// Rekursionsschritt zu den Unterebenen
				tmp = getVerantwortlichAufgaben(benutzer, unterEbenen[i].getId());
				if (tmp != null) {
					for (int j2 = 0; j2 < tmp.length; j2++) {
						temp.add(tmp[j2]);
					}
				}
			}
			// Duplikate entfernen (entstehen wenn eine Ebene zwei mal im Baum hängt, Stichwort Hardlinks)
			for(int i=0; i<temp.size()-1; i++) {
				for(int j=i+1; j<temp.size(); j++) {
					if(temp.get(i) == temp.get(j)) {
						temp.remove(j);
						temp.trimToSize();
						j--;
					}
				}
			}
			// Ergebnis zurück (in der Rekursion nach oben) geben
			return temp.toArray(new Ebene[0]);
		} else {
			return null;
		}
	}

	/**
	 * Liefert ein Ebenen-Array der Aufgaben des übergebenen
	 * Benutzers(Redakteur) zurück.
	 * 
	 * @param benutzer
	 *            der Benutzer
	 * @return die Ebenen als Array
	 * @throws SQLException
	 * @throws ClassNotFoundException
	 */
	public Ebene[] getRedakteurAufgaben(Benutzer benutzer, int ebenenID) throws ClassNotFoundException, SQLException {
		ArrayList<Ebene> temp = new ArrayList<Ebene>();

		// Ebenen nach zugehoerigen Redakteur durchsuchen		
		Ebene[] unterEbenen = ebenenRepository.getNeusteEbeneNachId(ebenenID).getUnterelement();
		Ebene[] tmp;
		if (unterEbenen != null) {
			for (int i = 0; i < unterEbenen.length; i++) {
				// Prüfen ob diese Ebene eine aktuelle Aufgabe ist
				if (unterEbenen[i].getRedakteur() != null) {
					for (int j = 0; j < unterEbenen[i].getRedakteur().length; j++) {
						if (unterEbenen[i].getRedakteur()[j] == benutzer) {
							if (unterEbenen[i].getRedaktionszustand() == Ebene.ZUSTAND_GEAENDERT
								|| unterEbenen[i].getRedaktionszustand() == Ebene.ZUSTAND_AKZEPTIERT_DEZ2)
								temp.add(unterEbenen[i]);
						}	
					}
				}
				// Rekursionsschritt zu den Unterebenen
				tmp = getRedakteurAufgaben(benutzer, unterEbenen[i].getId());
				if (tmp != null) {
					for (int j2 = 0; j2 < tmp.length; j2++) {
						temp.add(tmp[j2]);
					}
				}
			}
			// Duplikate entfernen (entstehen wenn eine Ebene zwei mal im Baum hängt, Stichwort Hardlinks)
			for(int i=0; i<temp.size()-1; i++) {
				for(int j=i+1; j<temp.size(); j++) {
					if(temp.get(i) == temp.get(j)) {
						temp.remove(j);
						temp.trimToSize();
						j--;
					}
				}
			}
			// Ergebnis zurück (in der Rekursion nach oben) geben
			return temp.toArray(new Ebene[0]);
		} else {
			return null;
		}
		
	}

	/**
	 * Liefert ein Ebenen-Array der Aufgaben des übergebenen
	 * Benutzers(Redakteur) zurück.
	 * 
	 * @param benutzer
	 *            der Benutzer
	 * @return die Ebenen als Array
	 * @throws SQLException
	 * @throws ClassNotFoundException
	 */
	public Ebene[] getDezernat2Aufgaben(Benutzer benutzer, int ebenenID)
			throws ClassNotFoundException, SQLException {
		ArrayList<Ebene> temp = new ArrayList<Ebene>();

		// Ebenen nach zugehoerigen Redakteur durchsuchen		
		Ebene[] unterEbenen = ebenenRepository.getNeusteEbeneNachId(ebenenID).getUnterelement();
		Ebene[] tmp;
		
		
		if (unterEbenen != null) {
			for (int i = 0; i < unterEbenen.length; i++) {
				// Prüfen ob diese Ebene eine aktuelle Aufgabe ist
				if (unterEbenen[i].getRedaktionszustand() == Ebene.ZUSTAND_AKZEPTIERT_REDAKT|| unterEbenen[i].getRedaktionszustand() == Ebene.ZUSTAND_GEAENDERT)
					temp.add(unterEbenen[i]);
				// Rekursionsschritt zu den Unterebenen
				tmp = getDezernat2Aufgaben(benutzer, unterEbenen[i].getId());
				if (tmp != null) {
					for (int j2 = 0; j2 < tmp.length; j2++) {
						temp.add(tmp[j2]);
					}
				}
			}
			// Duplikate entfernen (entstehen wenn eine Ebene zwei mal im Baum hängt, Stichwort Hardlinks)
			for(int i=0; i<temp.size()-1; i++) {
				for(int j=i+1; j<temp.size(); j++) {
					if(temp.get(i) == temp.get(j)) {
						temp.remove(j);
						temp.trimToSize();
						j--;
					}
				}
			}
			// Ergebnis zurück (in der Rekursion nach oben) geben
			return temp.toArray(new Ebene[0]);
		} else {
			return null;
		}
	}

	/**
	 * Durchsucht die Ebenen in dem Repository und der Datenbank nach einem
	 * String. Findet er diesen in einer Ebene oder zugehörigen Feldern, so wird
	 * diese Ebene in dem Ebenen Array zurückgegeben.
	 * 
	 * @param suche
	 *            der Begriff, nach dem in Feldern und Ebenen gesucht wird
	 * @return die Ebenen des Suchergebnisses als Array
	 * @throws SQLException
	 * @throws ClassNotFoundException
	 */
	public Ebene[] suche(String suche) throws ClassNotFoundException, SQLException {
		ArrayList<Ebene> ergebnis = new ArrayList<Ebene>(); // ergebnis liste
		ArrayList<Integer> punkte = new ArrayList<Integer>(); // Punkte Liste
		String[] wort = suche.toLowerCase().split(" "); // suche in einzelne wörter aufteilen
		ArrayList<Ebene> zuDurchsuchen = new ArrayList<Ebene>(); // noch zu durchsuchende Ebene, wächst während der Suche durch weitere Unterebenen an
		Ebene e;
		Ebene[] unter;
		Feld p;
		Feld[] f;
		int pos, trefferZahl, score;
		String s;
		// suche beginnen
		unter = getNeusteEbeneNachId(1).getUnterelement();
		if(unter != null) {
			if(unter.length > 0) {
				for(int i=0; i<unter.length; i++) {
					zuDurchsuchen.add(unter[i]);
				}
			}
		}
		while(zuDurchsuchen.size() > 0) {
			e = zuDurchsuchen.remove(zuDurchsuchen.size()-1);
			score = 0;
			for (int w=0; w<wort.length; w++) {
				if(wort[w] != "") { // nur nichtleere wörter suchen
					s = e.getName().toLowerCase(); // Namen durchsuchen
					trefferZahl = 0;
					pos = s.indexOf(wort[w]);
					while(pos != -1) {
						trefferZahl++;
						pos = s.indexOf(wort[w], pos+wort[w].length());
					}
					score += trefferZahl>0?400 * trefferZahl + 2 * wort[w].length():0;
					p = e.getProsa(); // Prosa durchsuchen
					if (p != null) {
						s = p.getWert().toLowerCase();
						trefferZahl = 0;
						pos = s.indexOf(wort[w]);
						while(pos != -1) {
							trefferZahl++;
							pos = s.indexOf(wort[w], pos+wort[w].length());
						}
						score += trefferZahl>0?200 * trefferZahl + 2 * wort[w].length():0;
					}
					// Felder durchsuchen
					f = e.getFeld();
					if(f != null) {
						trefferZahl = 0;
						for(int k=0; k<f.length; k++) {
							s = f[k].getWert().toLowerCase();
							pos = s.indexOf(wort[w]);
							while(pos != -1) {
								trefferZahl++;
								pos = s.indexOf(wort[w], pos+wort[w].length());
							}
						}
						score += trefferZahl>0?100 * trefferZahl + 2 * wort[w].length():0;
					}
				}
			}
			// Bei einem Treffer die Ebene dem Ergebnisarray hinzufügen und Punkte speichern (beide Werte in den verschiedenen ArrayLists haben die gleiche Indexposition)
			if(score > 0) {
				ergebnis.add(e);
				punkte.add(new Integer(score));
			}
			// alle Unterebenen der Ebene zum Durchsuchen merken
			unter = e.getUnterelement();
			if(unter != null) {
				if(unter.length > 0) {
					for(int i=0; i<unter.length; i++) {
						zuDurchsuchen.add(unter[i]);
					}
				}
			}
		}
		// kein Treffer -> return null
		if(ergebnis.size() == 0) {
			return null;
		}
		// doppelte Ebenen entfernen (passiert wenn eine Ebene an mehreren Stellen im Baum hängt, Stichwort: hardlink)
		for(int i=0; i<ergebnis.size()-1; i++) {
			for(int j=i+1; j<ergebnis.size(); j++) {
				if(ergebnis.get(i) == ergebnis.get(j)) {
					// duplikat gefunden -> entfernen
					ergebnis.remove(j);
					ergebnis.trimToSize();
					punkte.remove(j);
					punkte.trimToSize();
					j--;
				}
			}
		}
		// sortieren nach Trefferpunkten (also wie gut es auf den Suchbegriff passt)
		Ebene[] sortiert = new Ebene[ergebnis.size()];
		int max;
		for(int i=0; i<sortiert.length; i++) {
			max = 0;
			for(int j=1; j<ergebnis.size(); j++) {
				if(ergebnis.get(max) == null) {
					max = j;
				} else if(ergebnis.get(j) != null) {
					if(punkte.get(max) < punkte.get(j)) {
						max = j;
					}
				}
			}
			sortiert[i] = ergebnis.get(max);
			ergebnis.remove(max);
			punkte.remove(max);
		}
		return sortiert;
	}
	
	/**
	 * Liefert die Überebenen einer Ebene innerhalb des übergebenen (Teil-)Baumes.
	 * Tipp: aufrufen mit baum = Rootebene
	 * @param gesucht Die Ebene von der Überebenen gesucht werden.
	 * @param baum Der Baum in dem die Überebenen gesucht werden. Am besten mit der Rootebene (ID: 1) aufrufen. (Die Variable Baum ist eigentlich nur für die Rekursion wichtig)
	 * @return ebenen Die Überebenen.
	 * @throws ClassNotFoundException
	 * @throws SQLException
	 */
	public Ebene[] ueberEbenenFinden(Ebene gesucht, Ebene baum) throws ClassNotFoundException, SQLException{
		Ebene [] unterelement = baum.getUnterelement();
		if(unterelement == null) {
			return null;
		}
		ArrayList<Ebene> result = new ArrayList<Ebene>();
		Ebene[] tmp;
		for(int i=0; i<unterelement.length; i++) {
			if(unterelement[i].getId() == gesucht.getId()) { // Überebene gefunden
				result.add(baum);
			} else { // Überebene nicht gefunden -> Rekursionschritt: Unterebenen durchsuchen
				tmp = ueberEbenenFinden(gesucht, unterelement[i]);
				if(tmp != null) {
					for(int j=0; j<tmp.length; j++) {
						result.add(tmp[j]);
					}
				}
			}
		}
		if(result.size() == 0) {
			return null;
		}
		return result.toArray(new Ebene[0]);
		
	}
	
	/**
	 * Liefert eine Instanz des Repositorys zurück, damit dies nur einmal
	 * initiert wird.
	 * 
	 * @return EbenenRepository
	 * @throws ClassNotFoundException
	 * @throws SQLException
	 */
	public static EbenenRepository getInstance() throws ClassNotFoundException,
			SQLException {
		if (ebenenRepository == null) {
			ebenenRepository = new EbenenRepository();
		}
		return ebenenRepository;
	}
}
